package model;

import java.awt.GridLayout;
import java.util.ArrayList;

import javax.swing.JFrame;

import org.apache.commons.math3.ode.FirstOrderDifferentialEquations;
import org.apache.commons.math3.ode.nonstiff.GraggBulirschStoerIntegrator;
import org.apache.commons.math3.util.Pair;

import util.MyEventHandler;
import util.MyStepHandler;
import visualization.PlotExpData;

public class TicTacToeOligoSystem implements FirstOrderDifferentialEquations {

	OligoSystemComplex[][] table;
	int width = 3;
	int height = 3;
	ArrayList<Input[]> inputs = new ArrayList<Input[]>();
	double leakFactor = 0.005;
	static double leakThreshold = 1e-10;
	
	public TicTacToeOligoSystem(int noOfSimpleSeq, double[][][] template,
			double[][] seqK, double[][] inhK, double[][][] seqConcentration,
			double exo) {
		table = new OligoSystemComplex[width][height];
		for(int i=0;i<width;i++){
			for(int j=0;j<height;j++){
				table[i][j] = new OligoSystemComplex(noOfSimpleSeq, template, seqK, inhK, seqConcentration[i*height+j], null);
			}
		}
	}

	@Override
	public void computeDerivatives(double t, double[] y, double[] ydot) {
		// y is the current state, linearized from the states of all OligoSystems in order for(width) for(height) (= height first)
		// first, update concentrations without leaks
		int offset = 0;
		int dim = table[0][0].getDimension();
		
		double[] partialDerivatives = new double[dim];
		for(int i = 0; i < this.width; i++){
			for(int j = 0; j < this.height; j++){
				table[i][j].computePartialDerivatives(t, offset, y, partialDerivatives);
				for(int k = 0; k < dim; k++){
					ydot[offset+k]=partialDerivatives[k];
				}
				offset += dim;
			}
		}
		
		// add leaks
		dotheharlemshake(y,ydot);
		
	}

	private void dotheharlemshake(double[] y, double[] ydot) {
		// represent leaks
		int dim = table[0][0].getDimension();
		
		double mask = 0;
		String set;
		int offset = 0;
		//other = "";
		for(int i = 0; i < this.width; i++){
			for(int j = 0; j < this.height; j++){
				//other += y[offset] + " ";
				for(int k = 0; k < dim; k++){
					if(check(y[offset+k],y,k,getNeighbours(i,j))){
					
					// lost from leaks
					mask = - y[offset+k]*((double)getNeighbours(i,j).size());
					set = y[offset+k]+" "+mask;
					for(Pair<Integer,Integer> neighbour : getNeighbours(i,j)){
					 // from the neighbours
					mask += y[(neighbour.getKey()*height+neighbour.getValue())*dim+k];
					set += " "+y[(neighbour.getKey()*height+neighbour.getValue())*dim+k];
					}
						mask = Math.rint(mask/TicTacToeOligoSystem.leakThreshold)*TicTacToeOligoSystem.leakThreshold;
						ydot[offset+k] += this.leakFactor*mask; //Only multiply now, to avoid rounding problems: doesn't even slightly help
					
					}
				}
				offset += dim;
			}
		}
		//System.out.println(" Various values: "+other);
	}
	
	private boolean check(double d, double[] array, int k, ArrayList<Pair<Integer, Integer>> neighbours) {
		boolean transfert = false;
		int dim = table[0][0].getDimension();
		for(Pair<Integer,Integer> neigh : neighbours){
			if(d != array[(neigh.getKey()*height+neigh.getValue())*dim+k] ){
				transfert = true;
				break;
			}
		}
		return transfert;
	}

	private ArrayList<Pair<Integer,Integer>> getNeighbours(int i,int j){
		ArrayList<Pair<Integer,Integer>> neighbours = new ArrayList<Pair<Integer,Integer>>();
		if(i > 0){ // there is a left neighbour
			neighbours.add(new Pair<Integer,Integer>(i-1,j));
		}
		if(i < width - 1){ // there is a right neighbour
			neighbours.add(new Pair<Integer,Integer>(i+1,j));
		}
		if(j > 0){ // there is a up neighbour
			neighbours.add(new Pair<Integer,Integer>(i,j-1));
		}
		if(j < height - 1){ // there is a down neighbour
			neighbours.add(new Pair<Integer,Integer>(i,j+1));
		}
		return neighbours;
	}

	@Override
	public int getDimension() {
		
		// We should have a value equal to n*m times the value of just one system
		return width*height*table[0][0].getDimension();
	}
	
	public double[][] calculateTimeSeries() {

		GraggBulirschStoerIntegrator myIntegrator = new GraggBulirschStoerIntegrator(
				1e-10, 10000, 1e-6, 1e-6);
		
		
		//MyEventHandler event = new MyEventHandler(inputs);
		MyStepHandler handler = new MyStepHandler(table[0][0].total,
				null,9);
		myIntegrator.addStepHandler(handler);
		//myIntegrator.addEventHandler(event, 100, 1e-6, 100);
		int offset = 0;
		int dim = table[0][0].getDimension();
		double[] placeholder = new double[getDimension()];
		double[] temp;
		for(int i=0; i<this.width; i++){
			for(int j=0; j<this.height; j++){
				table[i][j].reinitializeOiligoSystem();
				temp = table[i][j].initialConditions();
				for(int k=0;k<dim;k++){
					placeholder[offset+k] = temp[k];
				}
				offset += dim;
			}
		}
		myIntegrator.integrate(this, 0, placeholder, 500,
				placeholder);

		// for (int t = 0; t < GAConstants.numberOfPoints; t++) {
		// where = 0;
		// outputModel.setInterpolatedTime(outputModel.getFinalTime()
		// * ((double) t / GAConstants.numberOfPoints));
		// //
		// System.out.println("value of the first sequence: "+outputModel.getInterpolatedState()[1]+" at time "+outputModel.getInterpolatedTime());
		// for (int i = 0; i < this.total; ++i) {
		// if (this.sequences.get(0).get(i) != null) {
		// series[where][t] = outputModel.getInterpolatedState()[i];
		// where++;
		// }
		// }
		// }
		return handler.getTimeSerie();
	}
	
	public static void main(String[] args){
		int nbSimple = 2; //Let's make a bistable
		double[][][] template = new double[3][2][2];
		template[0][0][0] = 10;
		template[0][1][1] = 10;
		template[1][1][1] = 20;
		template[2][0][0] = 20;
		double[][] seqK = new double[3][2];
		seqK[0][0] = 15;
		seqK[0][1] = 15;
		seqK[1][0] = 0.15;
		seqK[2][1] = 0.15;
		double[][] inhK = new double[3][2];
		inhK[1][0] = 0.45;
		inhK[2][1] = 0.45;
		double[][][] seqConcentration = new double[9][3][2];
		seqConcentration[0][0][0] = 1;
		seqConcentration[1][0][1] = 1;
		seqConcentration[2][0][1] = 1;
		seqConcentration[3][0][1] = 1;
		seqConcentration[4][0][0] = 1;
		seqConcentration[5][0][1] = 1;
		seqConcentration[6][0][1] = 1;
		seqConcentration[7][0][0] = 1;
		seqConcentration[8][0][0] = 1;
		double exo = 1;
		
		TicTacToeOligoSystem bi = new TicTacToeOligoSystem( nbSimple,  template,
				seqK,  inhK,  seqConcentration, exo);

//		String test = "";
//		for(int i=0; i<3; i++){
//			for(int j= 0; j<3; j++){
//				test+= bi.getNeighbours(i, j).size()+"\t";
//			}
//			test+="\n";
//		}
//		
//		System.out.println("Size neighborhood: \n"+test);
		
		JFrame frame = new JFrame("");
		//frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setSize(800, 600);
		double time1, time2;
		time1 = System.currentTimeMillis();
		
		double[][] time = bi.calculateTimeSeries();
		time2 = System.currentTimeMillis();
		System.out.println("done in "+(time2-time1));
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLayout(new GridLayout(3,3));
		double[][] separated = new double[2][];
		for(int i=0; i<3; i++){
			for(int j=0; j<3; j++){
				separated[0] = time[2*(i*3+j)];
				separated[1] = time[2*(i*3+j)+1];
				frame.add(new PlotExpData("TicTac"+(i*3+j),separated.clone()));
			}
		}
		//frame.pack();
		frame.setVisible(true);
	}

}
